/**
 *
 */
package com.honeybees.framework.common.util;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DecimalFormat;

/**
 * <dl>
 * <dt><b> 大数值的加、减、乘、除，四舍五入、取余、取模、开方，比较大小等运算。 </b></dt>
 * <p>
 * <dd>功能描述</dd>
 * </dl>
 * <p>
 * Copyright (C) All rights reserved.
 * </p>
 *
 * @author 李远明
 * @since 2015年10月25日 下午9:02:02
 */
public final class MathUtils {

    /**
     * 数字格式化
     */
    private static final String DECIMAL_PATTERN = "##,###,###.####";

    /**
     * <DL>
     * <DT><B> 将字符串表示的十进制数值转换成实数 </B></DT>
     * <p>
     * <DD>可以包含小数点、正负号、千分符等。</DD>
     * </DL>
     * <p>
     *
     * @param x
     *            大数字
     * @return {@link BigDecimal}
     * @version 2014-06-18 14:06:25
     */
    private static BigDecimal toBigDecimal(final String x) {
        String val = x.trim();
        val = val.replaceAll(",", "");

        if (val == null || val.length() == 0) {
            return new BigDecimal("0"); // 改成"0"，为了兼容Java 1.4
        }
        if (val.charAt(0) == '.') {
            return new BigDecimal("0" + val);
        }
        BigDecimal retVal;
        try {
            retVal = new BigDecimal(val);
        } catch (NumberFormatException e) {
            throw new NumberFormatException("不是合法的数值：" + x);
        }
        return retVal;
    }

    /**
     * <dl>
     * <dt><b> 将字符串表示的十进制整数转换成整数 </b></dt>
     * <p>
     * <dd>不能包含小数点，可以有正负号、千分符等。</dd>
     * </dl>
     *
     * @param x
     *            大整数
     * @return {@link BigInteger}
     * @author 李远明
     * @version 2015-02-02 22:09:02
     */
    private static BigInteger toBigInteger(final String x) {
        String val = x.trim();
        val = val.replaceAll(",", "");

        if (val == null || val.length() == 0) {
            return new BigInteger("0");
        }
        if (val.charAt(0) == '.') {
            throw new NumberFormatException("不是合法的整数：" + val);
        }
        BigInteger retVal;
        try {
            retVal = new BigInteger(val);
        } catch (NumberFormatException e) {
            throw new NumberFormatException("不是合法的整数：" + x);
        }
        return retVal;
    }

    /**
     * <DL>
     * <DT><B> 加 </B></DT>
     * <p>
     * <DD>可以将带千分符的两个字段相加</DD>
     * </DL>
     * <p>
     *
     * @param x
     *            加数（addend）
     * @param y
     *            被加数（augend）
     * @return x + y
     */
    public static String add(final String x, final String y) {
        BigDecimal result = toBigDecimal(x).add(toBigDecimal(y));
        return result.toString();
    }

    /**
     * <DL>
     * <DT><B> 减 </B></DT>
     * <p>
     * <DD></DD>
     * </DL>
     * <p>
     *
     * @param x
     *            被减数（minuend）
     * @param y
     *            减数（subtrahend）
     * @return x - y
     */
    public static String subtract(final String x, final String y) {
        BigDecimal result = toBigDecimal(x).subtract(toBigDecimal(y));
        return result.toString();
    }

    /**
     * <DL>
     * <DT><B> 乘 </B></DT>
     * <p>
     * <DD></DD>
     * </DL>
     * <p>
     *
     * @param x
     *            乘数（multiplier）
     * @param y
     *            被乘数（multiplicand）
     * @return x * y
     */
    public static String multiply(final String x, final String y) {
        return multiply(x, y, 0);
    }

    /**
     * <DL>
     * <DT><B> 乘 </B></DT>
     * <p>
     * <DD>指定小数位数</DD>
     * </DL>
     * <p>
     *
     * @param x
     *            乘数（multiplier）
     * @param y
     *            被乘数（multiplicand）
     * @param scale
     *            小数位数
     * @return x * y
     */
    public static String multiply(final String x, final String y, int scale) {
        BigDecimal multiplier = toBigDecimal(x); // 乘数
        BigDecimal multiplicand = toBigDecimal(y); // 被乘数

        if (scale <= 0) { // 如果保留小数位数小于零，计算结果将使用科学计数法表示 2016-05-20 11:12
            int scaleX = multiplier.scale();
            int scaleY = multiplicand.scale();
            scale = scaleX + scaleY; // 乘法结果的小数位数为乘数与被乘数小数位数的和
        }
        BigDecimal result = multiplier.multiply(multiplicand);
        result = result.setScale(scale, BigDecimal.ROUND_HALF_UP);
        return result.toString();
    }

    /**
     * <DL>
     * <DT><B> 除 </B></DT>
     * <p>
     * <DD>保留2位小数</DD>
     * <DD>被除数÷除数=商（x÷y=z）</DD>
     * </DL>
     * <p>
     *
     * @param x
     *            被除数（dividend）
     * @param y
     *            除数（divisor）
     * @return x / y
     */
    public static String divide(final String x, final String y) {
        return divide(x, y, 2);
    }

    /**
     * <DL>
     * <DT><B> 除 </B></DT>
     * <p>
     * <DD>指定小数位数</DD>
     * <DD>被除数÷除数=商（x÷y=z）</DD>
     * </DL>
     * <p>
     *
     * @param x
     *            被除数（dividend）
     * @param y
     *            除数（divisor）
     * @param scale
     *            小数点位数
     * @return x / y
     */
    public static String divide(final String x, final String y, int scale) {
        if (compare("0", y) == 0) { // 判断除数是否等于零
            throw new ArithmeticException("除数不能等于零（y = 0）");
        }
        BigDecimal dividend = toBigDecimal(x);
        BigDecimal divisor = toBigDecimal(y);

        if (scale <= 0) { // 如果保留小数位数小于零，计算结果将使用科学计数法表示 2016-05-20 11:12
            int scaleX = dividend.scale();
            int scaleY = divisor.scale();
            scale = Math.max(scaleX, scaleY); // 除法结果的小数位数取被除数与除数小数位数中更大的值
        }
        BigDecimal result = dividend.divide(divisor, scale, BigDecimal.ROUND_HALF_UP);
        return result.toString();
    }

    /**
     * <DL>
     * <DT><B> 四舍五入 </B></DT>
     * <p>
     * <DD>指定小数位数</DD>
     * </DL>
     * <p>
     *
     * @param x
     *            数值
     * @param scale
     *            保留小数位数
     * @return 四舍五入后的字符串
     */
    public static String round(final String x, int scale) {
        String val = x.replaceAll(",", "");
        if (scale < 0) {
            // throw new Exception("小数位数不能小于0");
            // System.out.println("小数位数不能小于0。");
            return val;
        }
        BigDecimal decimal;
        try {
            decimal = toBigDecimal(val);
        } catch (NumberFormatException e) {
            throw new NumberFormatException("不是合法的数值：" + x);
        }
        BigDecimal result = decimal.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
        return result.toString();
    }

    /**
     * <DL>
     * <DT><B> 取余（remainder） </B></DT>
     * <p>
     * <DD>求余是指一个数除以另一个数，不够除的部分就是余数，就是求余的结果。</DD>
     * <DD>该方法不是模操作，即其结果可能是负的。</DD>
     * </DL>
     * <p>
     *
     * @param x
     *            被除数（dividend）
     * @param y
     *            除数（divisor）
     * @return x % y （余数）
     * @see BigInteger#mod(BigInteger)
     * @see #mod(String, String)
     */
    public static String remainder(final String x, final String y) {
        BigDecimal result = toBigDecimal(x).remainder(toBigDecimal(y));
        return result.toString();
    }

    /**
     * <DL>
     * <DT><B> 判断一个数是否是另一个数的倍数 </B></DT>
     * <p>
     * <DD>整除就是一个数除以另一个数，刚刚好的倍数，这里没有四舍五入，不够一倍的都将舍去。</DD>
     * </DL>
     * <p>
     *
     * @param x
     *            被除数（dividend）
     * @param y
     *            除数（divisor）
     * @return x % y == 0 （ 如果是整倍数，返回true。）
     */
    // is a multiple of
    public static boolean isMultiple(final String x, final String y) {
        BigDecimal z = toBigDecimal(remainder(x, y));
        int result = z.compareTo(new BigDecimal("0"));
        return result == 0;
    }

    /**
     * <DL>
     * <DT><B> 比较大小 </B></DT>
     * <p>
     * <DD></DD>
     * </DL>
     * <p>
     *
     * @param x
     *            数值1
     * @param y
     *            数值2
     * @return 比较两个数值的大小：x﹦y返回0，x﹥y返回1，x﹤y返回-1
     */
    public static int compare(final String x, final String y) {
        int result = toBigDecimal(x).compareTo(toBigDecimal(y));
        return result;
    }

    /**
     * <DL>
     * <DT><B> 第一个数是否大于第二个数 </B></DT>
     * <p>
     * <DD></DD>
     * </DL>
     * <p>
     *
     * @param x
     *            数值1
     * @param y
     *            数值2
     * @return x > y
     * @version 2014-06-18 14:31:11
     */
    public static boolean isGreaterThan(final String x, final String y) {
        int result = compare(x, y);
        return result > 0;
    }

    /**
     * <DL>
     * <DT><B> 比较：<code>val</code>是否大于或等于零 </B></DT>
     * <p>
     * <DD></DD>
     * </DL>
     * <p>
     *
     * @param x
     *            数值
     * @return x >= 0
     * @version 2014-06-18 14:32:16
     */
    public static boolean isGreaterThan0(final String x) {
        int result = compare(x, "0");
        return result >= 0;
    }

    /**
     * <DL>
     * <DT><B> 取模（modulo） </B></DT>
     * <p>
     * <DD>求余与求摸的区别是，%所得的数有正有负，符号取决于左操作数，而mod只能是正。</DD>
     * <DD>a mod b = (a % b + b) % b，括号里的目的是把左操作数转成正数</DD>
     * </DL>
     * <p>
     *
     * @param x
     *            整数（integer）
     * @param y
     *            模数（整数）
     * @return x mod y
     * @author 李远明
     * @version 2015-02-02 22:05:02
     * @see BigDecimal#remainder(BigDecimal)
     * @see #remainder(String, String)
     * @see <a href="http://acm.hdu.edu.cn/showproblem.php?pid=1104">http://acm.hdu.edu.cn/
     *      showproblem.php?pid=1104</a>
     */
    public static String mod(final String x, final String y) {
        BigInteger result = toBigInteger(x).mod(toBigInteger(y));
        return result.toString();
    }

    /**
     * <dl>
     * <dt><b> 左移 </b></dt>
     * <p>
     * <dd>将 x 的值左移 n 位；n 应该为正数，否则，左移变成右移。</dd>
     * </dl>
     *
     * @param x
     *            整数（integer）
     * @param n
     *            左移的位数，应该大于零
     * @return x << n
     * @author 李远明
     * @version 2016-01-05 15:12:49
     * @see #shiftRight(String, int)
     */
    public static String shiftLeft(final String x, final int n) {
        BigInteger result = toBigInteger(x).shiftLeft(n);
        return result.toString();
    }

    /**
     * <dl>
     * <dt><b> 右移 </b></dt>
     * <p>
     * <dd>将 x 的值右移 n 位；n 应该为正数，否则，右移变成左移。</dd>
     * </dl>
     *
     * @param x
     *            整数（integer）
     * @param n
     *            右移的位数，应该大于零
     * @return x >> n
     * @author 李远明
     * @version 2016-01-05 15:13:41
     * @see #shiftLeft(String, int)
     */
    public static String shiftRight(final String x, final int n) {
        BigInteger result = toBigInteger(x).shiftRight(n);
        return result.toString();
    }

    /**
     * <dl>
     * <dt><b> 乘方 </b></dt>
     * <p>
     * <dd>求n个相同因数乘积的运算，叫做乘方，乘方的结果叫做幂（power）。</dd>
     * </dl>
     *
     * @param x
     *            底数（base number）
     * @param n
     *            指数（exponent）
     * @return {@code x}<sup>{@code n}</sup>（<tt>0<sup>0</sup></tt>=0）
     * @author 李远明
     * @version 2016-05-20 13:45:10
     */
    public static String pow(final String x, final int n) {
        if (n < 0 || n > 999999999) {
            throw new ArithmeticException("指数的值不合法。指数的范围为[0~999999999]。");
        }
        BigDecimal result = toBigDecimal(x).pow(n);
        return result.toString();
    }

    /**
     * <dl>
     * <dt><b> 开平方 </b></dt>
     * <p>
     * <dd>开方（rooting），指求一个数的方根的运算，为乘方的逆运算，在中国古代也指求二次及高次方程（包括二项方程）的正根。</dd>
     * <p>
     * <dd>平方：square rooting</dd>
     * <dd>立方：cube rooting</dd>
     * </dl>
     *
     * @param x
     *            数值
     * @return x 的平方根
     * @author 李远明
     * @version 2016-06-01 09:06:31
     * @see Math#sqrt(double)
     * @see Math#cbrt(double)
     */
    public static String sqrt(final String x) {
        BigDecimal numeric = toBigDecimal(x);
        BigDecimal result = sqrt(numeric);
        return result.toString();
    }

    /**
     * <dl>
     * <dt><b> 开方 </b></dt>
     * <p>
     * <dd>开方（rooting），指求一个数的方根的运算，为乘方的逆运算，在中国古代也指求二次及高次方程（包括二项方程）的正根。</dd>
     * <p>
     * <dd>平方：square rooting</dd>
     * <dd>立方：cube rooting</dd>
     * </dl>
     *
     * @param x
     *            数值
     * @return x 的平方根
     * @author 李远明
     * @version 2016-06-01 09:06:31
     */
    public static String sqrt(final int x) {
        String val = String.valueOf(x);
        BigDecimal numeric = toBigDecimal(val);
        BigDecimal result = sqrt(numeric);
        return result.toString();
    }

    /**
     * <dl>
     * <dt><b> 开方 </b></dt>
     * <p>
     * <dd>开方（rooting），指求一个数的方根的运算，为乘方的逆运算，在中国古代也指求二次及高次方程（包括二项方程）的正根。</dd>
     * <p>
     * <dd>平方：square rooting</dd>
     * <dd>立方：cube rooting</dd>
     * </dl>
     *
     * @param x
     *            数值
     * @return x 的平方根
     * @author 李远明
     * @version 2016-06-01 09:11:54
     */
    public static String sqrt(final long x) {
        String val = String.valueOf(x);
        BigDecimal numeric = toBigDecimal(val);
        BigDecimal result = sqrt(numeric);
        return result.toString();
    }

    /**
     * <dl>
     * <dt><b> 开方 </b></dt>
     * <p>
     * <dd>开方（rooting），指求一个数的方根的运算，为乘方的逆运算，在中国古代也指求二次及高次方程（包括二项方程）的正根。</dd>
     * <p>
     * <dd>平方：square rooting</dd>
     * <dd>立方：cube rooting</dd>
     * </dl>
     *
     * @param x
     *            数值
     * @return number 的平方根
     * @author 李远明
     * @version 2016-06-01 09:06:13
     */
    private static BigDecimal sqrt(final BigDecimal x) {
        BigDecimal temp1 = null;
        BigDecimal temp2 = null;

        int extraPrecision = x.precision();
        MathContext mc = new MathContext(extraPrecision, RoundingMode.HALF_UP);
        double num = x.doubleValue();
        if (mc.getPrecision() == 0) {
            throw new IllegalArgumentException("\nRoots need a MathContext precision > 0");
        }
        if (num < 0.0D) {
            throw new ArithmeticException("\nCannot calculate the square root of a negative number");
        }
        if (num == 0.0D) {
            return x.round(mc);
        }
        if (mc.getPrecision() < 50) {
            extraPrecision += 10;
        }
        int startPrecision = 1;
        BigDecimal iteration1; // 迭代
        BigDecimal iteration2; // 迭代
        if (num == (1.0D / 0.0D)) { // Infinity，表示正无穷大
            BigInteger integer = x.unscaledValue();
            int biLen = integer.bitLength();
            int biSqrtLen = biLen / 2;

            integer = integer.shiftRight(biSqrtLen);
            iteration1 = new BigDecimal(integer);

            MathContext mm = new MathContext(5, RoundingMode.HALF_DOWN);
            extraPrecision += 10;

            final BigDecimal TWO = new BigDecimal("2");
            iteration2 = BigDecimal.ONE.divide(TWO.multiply(iteration1, mm), mm);
        } else {
            double sqrt = Math.sqrt(num);
            iteration1 = new BigDecimal(Double.valueOf(sqrt).toString());
            iteration2 = new BigDecimal(Double.valueOf(0.5D / sqrt).toString());

            startPrecision = 64; // 保留64位数字
        }
        int digits = mc.getPrecision() + extraPrecision;

        MathContext n = new MathContext(startPrecision, mc.getRoundingMode());
        return sqrtProcedure(n, digits, x, iteration1, iteration2, temp1, temp2);
    }

    private static BigDecimal sqrtProcedure(MathContext mc, int digits, BigDecimal numberToBeSquareRooted,
            BigDecimal iteration1, BigDecimal iteration2, BigDecimal temp1, BigDecimal temp2) {
        final BigDecimal TWO = new BigDecimal("2");
        temp1 = BigDecimal.ONE.subtract(TWO.multiply(iteration1, mc).multiply(iteration2, mc), mc);
        iteration2 = iteration2.add(temp1.multiply(iteration2, mc), mc);

        temp2 = numberToBeSquareRooted.subtract(iteration1.multiply(iteration1, mc), mc);
        iteration1 = iteration1.add(temp2.multiply(iteration2, mc), mc);

        int m = mc.getPrecision();
        if (m < 2) {
            m++;
        } else {
            m = m * 2 - 1;
        }
        if (m < (2 * digits)) {
            mc = new MathContext(m, mc.getRoundingMode());
            sqrtProcedure(mc, digits, numberToBeSquareRooted, iteration1, iteration2, temp1, temp2);
        }
        return iteration1;
    }

    /**
     * <dl>
     * <dt><b> 数值格式化 </b></dt>
     * <p>
     * <dd>用千分号分割，保留四位小数点</dd>
     * <dd>4595305491221.93404 ==> 4,595,305,491,221.934</dd>
     * </dl>
     *
     * @param x
     *            要格式化的数字
     * @return 格式化后的字符串
     * @author 李远明
     * @version 2016年7月7日 上午11:27:30
     */
    public static String format(String x) {
        DecimalFormat format = new DecimalFormat(DECIMAL_PATTERN);
        BigDecimal number = new BigDecimal(x);
        String result = format.format(number);
        return result;
    }

    /**
     * <dl>
     * <dt><b> 数值格式化 </b></dt>
     * <p>
     * <dd>用千分号分割，保留四位小数点</dd>
     * <dd>4595305491221.93404 ==> 4,595,305,491,221.934</dd>
     * </dl>
     *
     * @param x
     *            要格式化的数字
     * @return 格式化后的字符串
     * @author 李远明
     * @version 2016年7月7日 上午11:36:11
     */
    public static String format(double x) {
        DecimalFormat format = new DecimalFormat(DECIMAL_PATTERN);
        BigDecimal number = new BigDecimal(x);
        String result = format.format(number);
        return result;
    }

    /**
     * <dl>
     * <dt><b> 数值格式化 </b></dt>
     * <p>
     * <dd>用千分号分割，保留四位小数点</dd>
     * <dd>4595305491221.93404 ==> 4,595,305,491,221.934</dd>
     * </dl>
     *
     * @param x
     *            要格式化的数字
     * @return 格式化后的字符串
     * @author 李远明
     * @version 2016年7月7日 上午11:37:23
     */
    public static String format(long x) {
        DecimalFormat format = new DecimalFormat(DECIMAL_PATTERN);
        BigDecimal number = new BigDecimal(x);
        String result = format.format(number);
        return result;
    }

    /**
     * <DL>
     * <DT><B> 标题 </B></DT>
     * <p>
     * <DD>x<sup>y</sup></DD>
     * </DL>
     * <p>
     *
     * @param args
     * @throws Exception
     * @version 2014-06-18 13:52:19
     */
    public static void main(String[] args) throws Exception {
        System.out.println(compare("99", "98")); // 1
        System.out.println(compare("99", "99")); // 0
        System.out.println(compare("98", "99")); // -1

        System.out.println(isGreaterThan("100", "88")); // true
        System.out.println(isGreaterThan0("1")); // true
        System.out.println(isGreaterThan0("0")); // true

        System.out.println(round("5,996.888", 2)); // 5996.89

        System.out.println(add("9,999,440.093", "459,556")); // 10458996.093

        System.out.println(remainder("-5", "3")); // -2
        System.out.println(mod("-5", "3")); // 1

        System.out.println(shiftLeft("12", 3)); // 96
        System.out.println(shiftLeft("12", -3)); // 1

        System.out.println(shiftRight("12", 3)); // 1
        System.out.println(shiftRight("12", -3)); // 96

        System.out.println(pow("398439", 16 - 5));

        double sqrt = Math.sqrt(5_299_999.92);
        System.out.println("sqrt: " + sqrt);

        String val = String.valueOf(sqrt);
        String multiply1 = multiply(val, val);
        System.out.println("multiply1: " + multiply1);
        String multiply2 = multiply(val, val, 2);
        System.out.println("multiply2: " + multiply2);

        // 开方 2016-06-01 09:51
        System.out.println(sqrt("25029.375"));
        System.out.println(sqrt(25));

        String ret = format("4595305491221.93404");
        System.out.println(ret);

        String time = format(1467862909533D);
        System.out.println(time);
    }

}
